#!/usr/bin/python
import cv2
import time
import sys
import threading
import requests
import base64,glob
import json,os
import numpy as np
import configparser
sys.path.insert(0, "..")
from opcua import Client

def initImg():
    '''
    初始化Img，为一张1280*720的纯白图片，空框测试结果为非空
    :return: img
    '''
    b_channel = np.ones((720, 1280), dtype=np.uint8) * 255
    g_channel = np.ones((720, 1280), dtype=np.uint8) * 255
    r_channel = np.ones((720, 1280), dtype=np.uint8) * 255
    image = cv2.merge((b_channel, g_channel, r_channel))
    return image

# opencv读取出来的图片相当于numpy数组
def cv2_to_base64(image):
    '''
    将输入的图片用base64编码
    :param image: 输入图片
    :return: 编码后的图片
    '''
    image1 = cv2.imencode('.jpg', image)[1]
    image_code = str(base64.b64encode(image1))[2:-1]
    return image_code

def getConfigFile(fatherNode,sonNode):
    '''
    从配置文件中读取opcua服务器的地址，用户名,密码,结点路径和心跳位路径
    :return:服务器的地址，用户名,密码,结点路径和心跳位路径
    '''
    config = configparser.ConfigParser()
    config.read('configfile.ini',encoding='utf-8')
    data = config[fatherNode][sonNode]
    return data

def getAllAera(contours,img):
    '''
    获取轮廓总面积
    :param contours: 图像轮廓
    :param img: 图像
    :return: 总面积，画上轮廓的图像
    '''
    areaAll=0
    for i in contours:
        # 最小矩形包围框
        ret = cv2.minAreaRect(i)
        # 得出矩形框的四个顶点
        pts = cv2.boxPoints(ret)
        pts = np.int0(pts)
        cv2.drawContours(img, i, -1, (0, 255, 0), 2)
        # 得出矩形框面积
        area = cv2.contourArea(pts)
        areaAll += area
    return areaAll,img

def changeFlag(flagValue):
    '''
    改变Flag值
    :param flagValue: 要改变的值
    :return:
    '''
    global Flag,Flag_lock
    Flag_lock.acquire()
    Flag = flagValue
    Flag_lock.release()

def opcuaClient():
    '''
    与opcua服务器建立连接
    若检测到flag为Ture，则代表空框
    将结点值置设为True，并每隔一段时间给心跳位置一
    '''
    global Flag
    # 是否连上opcua服务器
    connected = False
    # Flag记忆位，当保持空框状态时，只给结点置一一次
    memoFlag = False
    # 记录结点置一状态
    memoNode = False
    # 空框状态计数
    count = 0
    # 定义多久向心跳标志位置一
    heartbeatCount = 0
    while True:
        if connected:
            try:
                # 如果空框标志位为True，Flag记忆位为False，即从非空框转为空框时，则将空框计数位置一
                if Flag == True and memoFlag == False:
                    count = 1

                # 如果空框标志位为True，Flag记忆位为True，即空框状态保持时，则将空框计数加一
                elif Flag == True and memoFlag == True:
                    count+=1
                # 如果空框标志位为False,即非空框时，则将空框计数位清零,memoNode置False
                else:
                    count = 0
                    memoNode = False
                if count>= 5:
                    # 防止空旷时间过长count溢出
                    count = 5
                    # 如果结点此前没有被置一，则置一
                    if not memoNode:
                        node.set_value(True)
                        memoNode = True
                memoFlag = Flag
                    # changeFlag(False)
                # 心跳标志位置一
                if (heartbeatCount >= 5):
                    heartbeatnode.set_value(True)
                    heartbeatCount = 0
                heartbeatCount += 1
                time.sleep(0.1)
            except BaseException as e:
                connected = False
                if work_level==1:
                    print(e)
        else:
            #获得建立连接的参数
            url=getConfigFile('opcuaServer','url')
            username=getConfigFile('opcuaServer','username')
            password=getConfigFile('opcuaServer','password')
            nodepath=json.loads(getConfigFile('opcuaServer','nodepath'))
            heartbeat=json.loads(getConfigFile('opcuaServer','heartbeat'))
            #设置地址，用户名，密码
            client = Client(url)
            client.set_user(username=username)
            client.set_password(password)
            #连接服务器
            try:
                res = client.connect()
                root = client.get_root_node()
                node = root.get_child(nodepath)
                heartbeatnode = root.get_child(heartbeat)
                #进入死循环，不断判断Flag,并每隔一段时间给心跳标志位置一
                connected = True
            except BaseException as e:
                if work_level==1:
                    print(e)

def linkCamera():
    '''
    连接相机，将相机的图像裁剪后输出到全局变量Img
    :return:
    '''
    global Img,Img_lock,isCameraLinked
    url = getConfigFile('camera','url')
    ymin = int(getConfigFile('camera', 'ymin'))
    ymax = int(getConfigFile('camera', 'ymax'))
    xmin = int(getConfigFile('camera', 'xmin'))
    xmax = int(getConfigFile('camera', 'xmax'))
    # 使用rtsp地址打开摄像头
    cap = cv2.VideoCapture(url)
    # 检查相机是否打开
    while False == cap.isOpened():
        if work_level == 1: print("断开连接，自主重连")
        isCameraLinked = False
        cap = cv2.VideoCapture(url)
        ret, frame = cap.read()
    else:
        isCameraLinked=True
    # 若为调试模式，则打印一次isCameraLinked
    if work_level==1:print("相机连接状态：",isCameraLinked)
    # 显示图像
    while True:
        try:
            ret, frame = cap.read()
            if ret is False:
                # 若为调试模式，打印工作状态
                if work_level == 1: print("断开连接，自主重连")
                cap = cv2.VideoCapture(url)
                ret, frame = cap.read()
            # 裁剪图像
            frame = frame[ymin:ymax, xmin:xmax]
            # 裁剪过的图像写入全局变量，供其他线程使用
            Img_lock.acquire()
            Img = frame
            Img_lock.release()
            if isCameraLinked == False:
                isCameraLinked = True
            time.sleep(0.001)
        except BaseException as e:
            # 若为调试模式，打印错误信息
            if work_level == 1:
                print(e)
                print("相机重连")
                print("若重连无效，建议重启")
            # 若相机断开，自主重连
            cap = cv2.VideoCapture(url)
            ret, frame = cap.read()

def emptyDetection():
    global Img,Flag,Flag_lock,work_level
    # 空框检测灵敏度
    rate = float(getConfigFile('emptyBasketDetection','rate'))
    while True:
        # 相机连接上了才处理
        if isCameraLinked:
            start = time.time()
            img = Img
            # 原图转为灰度图
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            # 用Canny算法将灰度图转为二值图
            edge_output = cv2.Canny(gray, 50, 200)
            # 轮廓发现
            contours, hierarchy = cv2.findContours(edge_output, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
            # 获取轮廓总面积和画上轮廓的图像
            areaAll,img = getAllAera(contours,img)
            # 检测为非空框时
            if areaAll / (edge_output.shape[0] * edge_output.shape[1]) > rate:
                changeFlag(False)
            # 检测为空框时
            else:
                changeFlag(True)
            # 若为调试模式，则记录处理一次图片的用时
            if work_level == 1:
                end = time.time()
                usedTime = end - start
                # 向allData写入的内容依次为：画上轮廓的图片，空旷判断结果，图片处理时间
                allDAta_lock.acquire()
                allData[0] = img
                allData[1] = Flag
                allData[2] = usedTime
                allData[3] = time.time()
                allDAta_lock.release()
                # if Flag :
                #     print("空框")
                # else:
                #     print("非空框")
            time.sleep(0.1)
            # 线程只暂停一次0.1秒
            continue

        time.sleep(0.1)

def http_Client():
    '''
    连接http服务器，在调试模式下向http服务器上传数据
    :return:
    '''
    httpServerUrl = getConfigFile('httpServer','url')
    # 调试模式才上传
    if work_level == 1:
        while True:
            # 获得待上传的图片并编码
            image_code = cv2_to_base64(allData[0])
            img = []
            img.append(image_code)
            # 空框判断结果
            if allData[1]:
                result = 'empty'
            else:
                result = 'notempty'
            # 处理图片耗时
            usedTime = allData[2]
            timeNow = allData[3]
            try:
                # 打包成json上传
                files = {'image': img, 'result': result, 'usedTime': usedTime,'timeNow': timeNow}
                jsonFile = json.dumps(files,ensure_ascii=False)
                r = requests.post(httpServerUrl,json=jsonFile)
            except BaseException as e:
                if work_level==1:
                    print(e)
            time.sleep(0.1)
    else:
        return

'''
全局变量
Img：要处理的图片
Flag：是否空框标志位
work_level:工作模式
'''
Img = initImg()
isCameraLinked=False
Flag = False
work_level = int(getConfigFile('work_level','work_level'))
# 设置锁对应不同全局变量
Flag_lock = threading.Lock()
Img_lock = threading.Lock()
allDAta_lock = threading.Lock()
#调试模式下上传给服务器的数据,内容依次为：画上轮廓的图片，空旷判断结果，图片处理时间
allData = [Img,False,0.0,0]
if __name__ == '__main__':
    # 创建三个线程
    camera_thread = threading.Thread(target=linkCamera)
    main_thread = threading.Thread(target=emptyDetection)
    client_thread = threading.Thread(target=opcuaClient)
    http_thread = threading.Thread(target=http_Client)
    camera_thread.start()
    client_thread.start()
    main_thread.start()
    http_thread.start()


